﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Net;
using System.Threading;

namespace NetworkingLibrary.Clients.Combined
{
    public interface IUDPTCPCombinedClient : Clients.TCP.ITCPClient, Clients.UDP.IUDPClient
    {
        event Events.ClientConnectDelegate OnConnectTCP;
        event Events.ClientDisconnectDelegate OnDisconnectTCP;
        event Events.MessageReceivedDelegate OnMessageReceivedTCP;

        event Events.ClientConnectDelegate OnConnectUDP;
        event Events.ClientDisconnectDelegate OnDisconnectUDP;
        event Events.MessageReceivedDelegate OnMessageReceivedUDP;

        int TCPBufferSize
        {
            get;
            set;
        }
        int UDPBufferSize
        {
            get;
            set;
        }

        bool TCPTerminating
        {
            get;
            set;
        }
        bool UDPTerminating
        {
            get;
            set;
        }

        bool TCPConnected
        {
            get;
        }
        bool UDPConnected
        {
            get;
        }

        IPAddress TCPRemoteAddress
        {
            get;
        }
        IPAddress UDPRemoteAddress
        {
            get;
        }

        bool ConnectBoth(int TCPHostPort, IPAddress TCPHostAddress, int UDPHostPort, System.Net.IPAddress UDPHostAddress, bool ShouldStart = true, bool UDPIsMulticast = false, int UDPTimeToLive = 0);
        bool ConnectBoth(int TCPHostPort, IPAddress TCPHostAddress, int UDPHostPort, System.Net.IPAddress UDPHostAddress, bool TCPShouldStart = true, bool UDPShouldStart = true, bool UDPIsMulticast = false, int UDPTimeToLive = 0);

        bool DisconnectTCP(bool IgnoreSocketState = false);
        bool DisconnectUDP(bool IgnoreSocketState = false);

        bool StopTCP();
        bool StopUDP();

        bool Send(byte[] Data, bool UseUDP);
        bool Send(Messages.MessageObject TheMessage, bool UseUDP);
    }
    public class UDPTCPCombinedClient : IUDPTCPCombinedClient
    {
        private TCP.TCPClient TheTCPClient = null;
        private UDP.UDPClient TheUDPClient = null;
        
        public int MaxFlowRate
        {
            get
            {
                return TheTCPClient.MaxFlowRate > TheUDPClient.MaxFlowRate ? TheTCPClient.MaxFlowRate : TheUDPClient.MaxFlowRate;
            }
            set
            {
                TheUDPClient.MaxFlowRate = TheTCPClient.MaxFlowRate = value;
            }
        }

        public bool FlowControl
        {
            get
            {
                return TheTCPClient.FlowControl;
            }
            set
            {
                TheTCPClient.FlowControl = value;
            }
        }
        public DateTime LastSendTime
        {
            get
            {
                return TheTCPClient.LastSendTime > TheUDPClient.LastSendTime ? TheTCPClient.LastSendTime : TheUDPClient.LastSendTime;
            }
            set
            {
                TheUDPClient.LastSendTime = TheTCPClient.LastSendTime = value;
            }
        }

        public event Events.ClientConnectDelegate OnConnect = delegate { };
        public event Events.ClientDisconnectDelegate OnDisconnect = delegate { };
        public event Events.MessageReceivedDelegate OnMessageReceived = delegate { };
        public event Events.ErrorDelegate OnError = delegate { };

        public event Events.ClientConnectDelegate OnConnectTCP = delegate { };
        public event Events.ClientDisconnectDelegate OnDisconnectTCP = delegate { };
        public event Events.MessageReceivedDelegate OnMessageReceivedTCP = delegate { };

        public event Events.ClientConnectDelegate OnConnectUDP = delegate { };
        public event Events.ClientDisconnectDelegate OnDisconnectUDP = delegate { };
        public event Events.MessageReceivedDelegate OnMessageReceivedUDP = delegate { };

        public Guid ID
        {
            get;
            set;
        }

        public int BufferSize
        {
            get
            {
                return TheTCPClient.BufferSize > TheUDPClient.BufferSize ? TheTCPClient.BufferSize : TheUDPClient.BufferSize;
            }
            set
            {
                TheUDPClient.BufferSize = TheTCPClient.BufferSize = value;
            }
        }
        public int TCPBufferSize
        {
            get
            {
                return TheTCPClient.BufferSize;
            }
            set
            {
                TheTCPClient.BufferSize = value;
            }
        }
        public int UDPBufferSize
        {
            get
            {
                return TheUDPClient.BufferSize;
            }
            set
            {
                TheUDPClient.BufferSize = value;
            }
        }

        public bool Terminating
        {
            get
            {
                return (!TheTCPClient.Connected || TheTCPClient.Terminating) && (!TheUDPClient.Connected || TheUDPClient.Terminating);
            }
            set
            {
                TheUDPClient.Terminating = TheTCPClient.Terminating = value;
            }
        }
        public bool TCPTerminating
        {
            get
            {
                return (!TheTCPClient.Connected || TheTCPClient.Terminating);
            }
            set
            {
                TheTCPClient.Terminating = value;
            }
        }
        public bool UDPTerminating
        {
            get
            {
                return (!TheUDPClient.Connected || TheUDPClient.Terminating);
            }
            set
            {
                TheUDPClient.Terminating = value;
            }
        }

        public bool Connected
        {
            get
            {
                return TheTCPClient.Connected || TheUDPClient.Connected;
            }
        }
        public bool TCPConnected
        {
            get
            {
                return TheTCPClient.Connected;
            }
        }
        public bool UDPConnected
        {
            get
            {
                return TheUDPClient.Connected;
            }
        }

        public IPAddress LocalAddress
        {
            get
            {
                return TheUDPClient.LocalAddress;
            }
        }
        public IPAddress RemoteAddress
        {
            get
            {
                return TCPConnected ? TheTCPClient.RemoteAddress : (UDPConnected ? TheUDPClient.RemoteAddress : null);
            }
        }
        public IPAddress TCPRemoteAddress
        {
            get
            {
                return TCPConnected ? TheTCPClient.RemoteAddress : null;
            }
        }
        public IPAddress UDPRemoteAddress
        {
            get
            {
                return UDPConnected ? TheUDPClient.RemoteAddress : null;
            }
        }
        public IPAddress HostAddress
        {
            get
            {
                return TheUDPClient.HostAddress;
            }
            set
            {
                TheUDPClient.HostAddress = value;
            }
        }
        
        public int LocalPort
        {
            get
            {
                return TheUDPClient.LocalPort;
            }
            set
            {
                TheUDPClient.LocalPort = value;
            }
        }
        public int HostPort
        {
            get
            {
                return TheUDPClient.HostPort;
            }
        }

        public Utilities.UDP.UDPMessagingMode UDPMode
        {
            get
            {
                return TheUDPClient.UDPMode;
            }
            set
            {
                TheUDPClient.UDPMode = value;
            }
        }
    
        public UDPTCPCombinedClient(int UDPLocalPort)
        {
            Init(UDPLocalPort);
        }
        public UDPTCPCombinedClient(int UDPLocalPort, Events.MessageReceivedDelegate OnAMessageReceived)
        {
            Init(UDPLocalPort);

            OnMessageReceived += OnMessageReceived;
        }
        public UDPTCPCombinedClient(int UDPLocalPort, Events.MessageReceivedDelegate TCPOnAMessageReceived, Events.MessageReceivedDelegate UDPOnAMessageReceived)
        {
            Init(UDPLocalPort);

            OnMessageReceivedTCP += TCPOnAMessageReceived;
            OnMessageReceivedUDP += UDPOnAMessageReceived;
        }

        private void Init(int UDPLocalPort)
        {
            TheTCPClient = new TCP.TCPClient();
            TheUDPClient = new UDP.UDPClient(UDPLocalPort);

            TheTCPClient.OnConnect += new Events.ClientConnectDelegate(TheTCPClient_OnConnect);
            TheTCPClient.OnDisconnect += new Events.ClientDisconnectDelegate(TheTCPClient_OnDisconnect);
            TheTCPClient.OnError += new Events.ErrorDelegate(TheTCPClient_OnError);
            TheTCPClient.OnMessageReceived += new Events.MessageReceivedDelegate(TheTCPClient_OnMessageReceived);

            TheUDPClient.OnConnect += new Events.ClientConnectDelegate(TheUDPClient_OnConnect);
            TheUDPClient.OnDisconnect += new Events.ClientDisconnectDelegate(TheUDPClient_OnDisconnect);
            TheUDPClient.OnError += new Events.ErrorDelegate(TheUDPClient_OnError);
            TheUDPClient.OnMessageReceived += new Events.MessageReceivedDelegate(TheUDPClient_OnMessageReceived);
        }

        private void TheUDPClient_OnMessageReceived(Events.MessageReceivedEventArgs e)
        {
            OnMessageReceived.Invoke(e);
            OnMessageReceivedUDP.Invoke(e);
        }
        private void TheUDPClient_OnError(Events.ErrorEventArgs e)
        {
            OnError.Invoke(e);
        }
        private void TheUDPClient_OnDisconnect(Events.ClientDisconnectEventArgs e)
        {
            OnDisconnect.Invoke(e);
            OnDisconnectUDP.Invoke(e);
        }
        private void TheUDPClient_OnConnect(Events.ClientConnectEventArgs e)
        {
            OnConnect.Invoke(e);
            OnConnectUDP.Invoke(e);
        }

        private void TheTCPClient_OnMessageReceived(Events.MessageReceivedEventArgs e)
        {
            OnMessageReceived.Invoke(e);
            OnMessageReceivedTCP.Invoke(e);
        }
        private void TheTCPClient_OnError(Events.ErrorEventArgs e)
        {
            OnError.Invoke(e);
        }
        private void TheTCPClient_OnDisconnect(Events.ClientDisconnectEventArgs e)
        {
            OnDisconnect.Invoke(e);
            OnDisconnectTCP.Invoke(e);
        }
        private void TheTCPClient_OnConnect(Events.ClientConnectEventArgs e)
        {
            OnConnect.Invoke(e);
            OnConnectTCP.Invoke(e);
        }

        public bool Connect(int TCPHostPort, IPAddress TCPHostAddress, bool ShouldStart = true)
        {
            try
            {
                return TheTCPClient.Connect(TCPHostPort, TCPHostAddress, ShouldStart);
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }
        public bool Connect(int UDPHostPort, System.Net.IPAddress UDPHostAddress, bool ShouldStart = true, bool UDPIsMulticast = false, int UDPTimeToLive = 0)
        {
            try
            {
                return TheUDPClient.Connect(UDPHostPort, UDPHostAddress, ShouldStart, UDPIsMulticast, UDPTimeToLive);
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }
        public bool ConnectBoth(int TCPHostPort, IPAddress TCPHostAddress, int UDPHostPort, System.Net.IPAddress UDPHostAddress, bool ShouldStart = true, bool UDPIsMulticast = false, int UDPTimeToLive = 0)
        {
            return ConnectBoth(TCPHostPort, TCPHostAddress, UDPHostPort, UDPHostAddress, ShouldStart, ShouldStart, UDPIsMulticast, UDPTimeToLive);
        }
        public bool ConnectBoth(int TCPHostPort, IPAddress TCPHostAddress, int UDPHostPort, System.Net.IPAddress UDPHostAddress, bool TCPShouldStart = true, bool UDPShouldStart = true, bool UDPIsMulticast = false, int UDPTimeToLive = 0)
        {
            try
            {
                bool OK = TheTCPClient.Connect(TCPHostPort, TCPHostAddress, TCPShouldStart);
                if (OK)
                {
                    if (!(OK = TheUDPClient.Connect(UDPHostPort, UDPHostAddress, UDPShouldStart, UDPIsMulticast, UDPTimeToLive)))
                    {
                        Disconnect();
                    }
                }
                return OK;
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }
        public bool Disconnect(bool IgnoreSocketState = false)
        {
            try
            {
                return TheTCPClient.Disconnect(IgnoreSocketState) && TheUDPClient.Disconnect(IgnoreSocketState);
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }
        public bool DisconnectTCP(bool IgnoreSocketState = false)
        {
            try
            {
                return TheTCPClient.Disconnect(IgnoreSocketState);
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }
        public bool DisconnectUDP(bool IgnoreSocketState = false)
        {
            try
            {
                return TheUDPClient.Disconnect(IgnoreSocketState);
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }

        public bool Start()
        {
            try
            {
                return TheTCPClient.Start();
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }
        public bool Start(int TimeToLive = 0)
        {
            try
            {
                return TheUDPClient.Start(TimeToLive);
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }
        public bool StartBoth(int TimeToLive = 0)
        {
            try
            {
                return TheTCPClient.Start() && TheUDPClient.Start(TimeToLive);
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }
        public bool Stop()
        {
            try
            {
                return TheTCPClient.Stop() && TheUDPClient.Stop();
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }
        public bool StopTCP()
        {
            try
            {
                return TheTCPClient.Stop();
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }
        public bool StopUDP()
        {
            try
            {
                return TheUDPClient.Stop();
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }

        public bool Send(byte[] Data)
        {
            return Send(Data, false);
        }
        public bool Send(Messages.MessageObject TheMessage)
        {
            return Send(TheMessage, false);
        }
        public bool Send(byte[] Data, bool UseUDP)
        {
            try
            {
                if (UseUDP)
                {
                    if (UDPConnected)
                    {
                        TheUDPClient.Send(Data);
                    }
                }
                else if (TCPConnected)
                {
                    TheTCPClient.Send(Data);
                }
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }
        public bool Send(Messages.MessageObject TheMessage, bool UseUDP)
        {
            try
            {
                if (UseUDP)
                {
                    if (UDPConnected)
                    {
                        TheUDPClient.Send(TheMessage);
                    }
                }
                else if(TCPConnected)
                {
                    TheTCPClient.Send(TheMessage);
                }
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }

        public bool SendTo(IPAddress To, int ToPort, byte[] Data)
        {
            try
            {
                if (UDPConnected)
                {
                    return TheUDPClient.SendTo(To, ToPort, Data);
                }
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }
        public bool SendTo(IPAddress To, int ToPort, Messages.MessageObject TheMessage)
        {
            try
            {
                if (UDPConnected)
                {
                    return TheUDPClient.SendTo(To, ToPort, TheMessage);
                }
            }
            catch (Exception ex)
            {
                OnError.Invoke(new Events.ErrorEventArgs(ex));
            }
            return false;
        }
        
        public Messages.MessageObject GetMessageResponse(Messages.MessageObject MessageToSend, Type WantedResponseType)
        {
            return TheTCPClient.GetMessageResponse(MessageToSend, WantedResponseType);
        }

    }
}
